{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对应`tf.kears` 版本的03，在训练过程中加入更多的控制\n",
    "\n",
    "1. 训练中保存/保存最好的模型\n",
    "2. 早停 \n",
    "3. 训练过程可视化\n",
    "\n",
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T01:57:02.285080600Z",
     "start_time": "2024-07-18T01:56:52.418388500Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.9.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.5.0\n",
      "torch 2.3.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)  #设备是cuda:0，即GPU，如果没有GPU则是cpu\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T01:57:51.324566200Z",
     "start_time": "2024-07-18T01:57:48.504779800Z"
    }
   },
   "outputs": [],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# fashion_mnist图像分类数据集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 当然也可以用 torch.utils.data.Dataset 实现人为划分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T01:57:53.071712500Z",
     "start_time": "2024-07-18T01:57:53.060015300Z"
    }
   },
   "outputs": [],
   "source": [
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32, shuffle=True)\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=32, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([32, 1, 28, 28])\n",
      "torch.Size([32])\n",
      "torch.Size([32, 1, 28, 28])\n",
      "torch.Size([32])\n"
     ]
    }
   ],
   "source": [
    "# 查看数据\n",
    "for datas, labels in train_loader:\n",
    "    print(datas.shape)\n",
    "    print(labels.shape)\n",
    "    break\n",
    "#查看val_loader\n",
    "for datas, labels in val_loader:\n",
    "    print(datas.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T01:58:03.924900200Z",
     "start_time": "2024-07-18T01:58:03.865225700Z"
    }
   }
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T02:01:18.583508Z",
     "start_time": "2024-07-18T02:01:18.561874500Z"
    }
   },
   "outputs": [],
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.flatten = nn.Flatten()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 300),  # in_features=784, out_features=300\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(300, 100),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(100, 10),\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 1, 28, 28]\n",
    "        x = self.flatten(x)  \n",
    "        # 展平后 x.shape [batch size, 28 * 28]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 10]\n",
    "        return logits\n",
    "    \n",
    "model = NeuralNetwork()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:00:32.064832600Z",
     "start_time": "2024-07-18T03:00:32.060565500Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        #datas.shape [batch size, 1, 28, 28]\n",
    "        #labels.shape [batch size]\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item()) # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测, axis=-1 表示最后一个维度,因为logits.shape [batch size, 10]，所以axis=-1表示对最后一个维度求argmax，即对每个样本的10个类别的概率求argmax，得到最大概率的类别, preds.shape [batch size]\n",
    "        pred_list.extend(preds.cpu().numpy().tolist()) # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list) # 验证集准确率\n",
    "    return np.mean(loss_list), acc # 返回验证集平均损失和准确率\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TensorBoard 可视化\n",
    "\n",
    "pip install tensorboard\n",
    "训练过程中可以使用如下命令启动tensorboard服务。注意使用绝对路径，否则会报错\n",
    "\n",
    "```shell\n",
    " tensorboard  --logdir=\"D:\\BaiduSyncdisk\\pytorch\\chapter_2_torch\\runs\" --host 0.0.0.0 --port 8848\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T02:43:40.404725900Z",
     "start_time": "2024-07-18T02:43:40.131435400Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs) # 实例化SummaryWriter, log_dir是log存放路径，flush_secs是每隔多少秒写入磁盘\n",
    "\n",
    "    def draw_model(self, model, input_shape):#graphs\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape)) # 画模型图\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            ) # 画loss曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        ) # 画acc曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "        ) # 画lr曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss,把loss，val_loss取掉，画loss曲线\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss) # 画loss曲线\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc) # 画acc曲线\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate) # 画lr曲线\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T02:43:42.457851800Z",
     "start_time": "2024-07-18T02:43:42.448940500Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir # 保存路径\n",
    "        self.save_step = save_step # 保存步数\n",
    "        self.save_best_only = save_best_only # 是否只保存最好的模型\n",
    "        self.best_metrics = -1 # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir): # 如果不存在保存路径，则创建\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0: #每隔save_step步保存一次\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None # 必须传入metric\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\")) # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\")) # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n",
    "\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T02:43:44.696462800Z",
     "start_time": "2024-07-18T02:43:44.684950500Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience # 多少个epoch没有提升就停止训练\n",
    "        self.min_delta = min_delta # 最小的提升幅度\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:#用准确率\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1 # 计数器加1，下面的patience判断用到\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "outputs": [
    {
     "data": {
      "text/plain": "80000"
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "500*32*5"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T02:43:54.503637Z",
     "start_time": "2024-07-18T02:43:54.488870100Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T02:45:10.645475Z",
     "start_time": "2024-07-18T02:45:10.620818400Z"
    }
   },
   "outputs": [],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device) # 数据放到device上\n",
    "                labels = labels.to(device) # 标签放到device上\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传，计算梯度，更新参数，这里是更新模型参数\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 切换到验证集模式\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train() # 切换回训练集模式\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"], # 取出当前学习率\n",
    "                            )\n",
    "                    \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc) # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc) # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:# 验证集准确率不再提升，则停止训练\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "tensorboard_callback = TensorBoardCallback(\"runs\")\n",
    "tensorboard_callback.draw_model(model, [1, 28, 28])\n",
    "# 2. save best\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10)\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T02:52:59.198747Z",
     "start_time": "2024-07-18T02:52:58.899229Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "outputs": [
    {
     "data": {
      "text/plain": "Parameter containing:\ntensor([ 8.8742e-03,  5.1779e-03, -2.5233e-02, -1.9432e-02, -2.6992e-02,\n        -1.0262e-02, -9.9559e-03, -2.8894e-02, -7.3424e-03,  2.4227e-04,\n         3.4269e-03,  1.6518e-02,  1.7700e-02, -3.5169e-02, -2.9928e-02,\n         3.0644e-02,  3.0822e-02, -1.6215e-02, -2.8830e-02, -3.4676e-02,\n         8.5081e-03,  4.6115e-03,  2.2893e-02,  2.2112e-04, -1.7842e-02,\n         2.7805e-03, -7.4717e-03,  1.9765e-02,  2.4835e-02,  3.3089e-02,\n        -2.8166e-02,  3.3634e-02, -3.3583e-02, -2.3294e-02, -3.2217e-02,\n        -3.4792e-02, -1.8947e-02, -1.6239e-02,  3.0975e-02, -2.0176e-02,\n        -1.1199e-02,  2.2809e-02, -1.6708e-02,  1.1415e-03,  9.1366e-03,\n        -3.3974e-02,  1.1891e-02,  5.6981e-03,  1.7544e-02, -2.9667e-03,\n        -1.3368e-02,  8.2697e-03,  2.4605e-02, -6.6776e-03, -6.5502e-03,\n        -5.7296e-03, -1.6775e-02,  2.3113e-02,  3.2304e-02, -5.5061e-03,\n        -2.1324e-02, -3.2872e-02, -2.0915e-02,  3.7288e-03, -1.8517e-02,\n        -1.0835e-02,  1.4550e-02, -2.8902e-02,  3.1606e-02, -7.4258e-03,\n        -7.0939e-03, -7.7739e-03,  2.4942e-02,  2.5878e-03,  2.0559e-02,\n        -3.4065e-02, -1.7029e-02, -2.0780e-02, -5.2240e-03, -2.3994e-02,\n         1.7925e-04,  1.6176e-03,  2.3428e-02, -3.0860e-02, -2.4441e-02,\n         2.9257e-02, -3.4501e-02, -1.9409e-02,  1.4360e-02,  3.0703e-02,\n         3.6210e-03, -2.5326e-02, -2.6064e-02, -2.0162e-02,  1.0782e-02,\n         2.9651e-02, -1.7817e-02, -2.9946e-02, -3.2913e-02, -3.0442e-02,\n        -4.5628e-03, -2.1763e-03,  2.8274e-02, -2.4409e-02,  3.2770e-03,\n         2.6578e-02,  2.6866e-02, -4.2900e-03,  2.5212e-02, -3.1917e-02,\n         9.8985e-03, -1.8577e-02, -1.5955e-02, -9.6611e-03, -1.1019e-02,\n         3.0086e-02, -3.1082e-02,  2.3156e-03, -8.7540e-03, -8.7338e-03,\n         1.8913e-02,  8.5582e-04, -6.7466e-03,  1.5792e-02,  1.2904e-02,\n        -1.8185e-02, -2.1215e-02,  3.1027e-02,  1.5608e-02, -2.1582e-02,\n        -1.6023e-02, -2.2706e-02,  3.1782e-02, -4.1106e-03,  1.3373e-02,\n         1.5806e-02, -9.9012e-03, -2.2690e-02, -2.7263e-02,  2.3756e-02,\n        -3.2707e-02,  4.3998e-03, -3.1107e-02,  1.8484e-02,  2.8024e-02,\n        -6.7135e-03, -2.3201e-02,  2.7178e-02,  3.0986e-02,  3.1031e-03,\n         2.2374e-02,  2.6722e-02,  2.5922e-02,  1.7343e-02,  4.1950e-03,\n        -9.2560e-03,  2.4808e-02,  6.9637e-03,  1.5302e-02, -3.4990e-02,\n        -1.3780e-02,  3.5149e-02,  9.6017e-04,  3.2900e-02, -1.8759e-02,\n        -3.2884e-02, -1.9005e-02,  6.2928e-03,  1.6630e-03,  3.4288e-02,\n         1.9338e-02, -1.6282e-02,  2.1420e-02,  1.9541e-02, -6.1658e-03,\n         1.2000e-02, -1.9283e-02,  2.2192e-02,  1.4970e-02,  1.0003e-02,\n        -2.8914e-02,  3.2865e-02, -3.0469e-02,  1.3464e-02,  4.2692e-03,\n         8.0944e-03,  2.0393e-02,  3.4222e-02, -9.0711e-03,  1.9172e-03,\n        -2.3879e-02, -2.0444e-02, -1.7830e-02,  2.4249e-02, -2.9244e-02,\n         3.5369e-02, -1.3147e-02,  3.5260e-02,  3.0852e-02,  1.5225e-02,\n         7.0268e-03, -2.8861e-02,  1.8405e-02, -3.0209e-02,  7.7201e-03,\n         2.2235e-02, -1.3252e-02, -1.1794e-02,  2.1412e-02, -2.4239e-02,\n        -2.1066e-02,  3.5399e-02, -4.2075e-03,  3.1534e-02,  1.2266e-02,\n        -3.2636e-02, -1.6539e-02, -5.7913e-03,  1.4461e-02, -1.3648e-02,\n         2.9895e-02,  2.8215e-02,  1.4264e-02, -7.6001e-03,  9.9021e-03,\n         3.5614e-02, -3.1923e-02, -3.1820e-02, -1.3839e-03, -1.2545e-03,\n         2.6839e-03,  2.7857e-02, -6.2959e-03,  1.7108e-02, -3.5207e-02,\n         9.5840e-04, -1.3959e-02, -3.4528e-02, -8.6865e-04,  2.6555e-02,\n        -3.3786e-02, -3.1822e-02,  3.2416e-02, -2.6091e-02,  1.3511e-02,\n        -8.3584e-03, -3.2912e-02, -3.0322e-02,  2.0524e-02,  1.1562e-02,\n        -3.3815e-02,  2.8786e-02,  4.0577e-03,  7.7953e-03, -2.6936e-02,\n        -3.5199e-02, -3.5455e-02, -2.6082e-02, -1.8129e-02, -2.3289e-02,\n         1.9710e-02, -3.4226e-02,  2.5055e-02, -2.1837e-02,  3.5078e-02,\n        -3.5530e-03, -9.8379e-03,  3.5584e-02, -5.3817e-03, -1.0042e-03,\n        -1.2494e-02, -3.4721e-02, -1.2660e-02, -3.3899e-02, -2.0982e-02,\n        -2.6582e-02, -2.2099e-02, -1.2423e-02, -1.7371e-02,  3.3379e-02,\n         2.7583e-02, -2.2449e-02,  6.2957e-07,  2.2755e-02,  1.8995e-02,\n        -1.7274e-02,  1.4615e-02,  2.4319e-03,  4.4142e-03,  1.3689e-02,\n        -1.6485e-02,  3.5662e-02, -2.3967e-02,  1.2126e-03, -1.9662e-02,\n        -2.0572e-02, -3.0864e-02,  4.8980e-03, -2.5857e-02, -1.3792e-02],\n       requires_grad=True)"
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(model.parameters())[1] #可学习的模型参数"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T02:58:22.680524700Z",
     "start_time": "2024-07-18T02:58:22.670552500Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "outputs": [
    {
     "data": {
      "text/plain": "odict_keys(['linear_relu_stack.0.weight', 'linear_relu_stack.0.bias', 'linear_relu_stack.2.weight', 'linear_relu_stack.2.bias', 'linear_relu_stack.4.weight', 'linear_relu_stack.4.bias'])"
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.state_dict().keys()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T02:55:25.887158200Z",
     "start_time": "2024-07-18T02:55:25.876289300Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=1000\n",
    "    )"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 a\n",
      "1 b\n",
      "2 c\n"
     ]
    }
   ],
   "source": [
    "#帮我写个enumerate例子\n",
    "for i, item in enumerate([\"a\", \"b\", \"c\"]):\n",
    "    print(i, item)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-04-23T07:26:19.957701200Z",
     "start_time": "2024-04-23T07:26:19.914720400Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-20T07:42:43.237109700Z",
     "start_time": "2023-11-20T07:42:43.010089800Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 720x360 with 2 Axes>",
      "image/png": "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\n"
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns) #因为有loss和acc两个指标，所以画个子图\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5)) #fig_num个子图，figsize是子图大小\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        x_data=range(0, train_df.index[-1], 5000) #每隔5000步标出一个点\n",
    "        axs[idx].set_xticks(x_data)\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", x_data)) #map生成labal\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "outputs": [],
   "source": [
    "model = NeuralNetwork() #上线时加载模型\n",
    "model = model.to(device)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T03:03:42.207695300Z",
     "start_time": "2024-07-18T03:03:42.202895700Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T03:03:47.249318Z",
     "start_time": "2024-07-18T03:03:44.700582800Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4264\n",
      "accuracy: 0.8471\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
